//// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
//package com.linqingying.gitcode.pullrequest.comment.ui
//
//import com.intellij.collaboration.async.combineState
//import com.intellij.collaboration.async.computationState
//import com.intellij.collaboration.async.launchNowIn
//import com.intellij.collaboration.ui.html.AsyncHtmlImageLoader
//import com.intellij.collaboration.util.SingleCoroutineLauncher
//import com.intellij.collaboration.util.getOrNull
//import com.intellij.diff.util.LineRange
//import com.intellij.diff.util.Side
//import com.intellij.openapi.diagnostic.logger
//import com.intellij.openapi.diff.impl.patch.*
//import com.intellij.openapi.diff.impl.patch.PatchHunkUtil.getLinesInRange
//import com.intellij.openapi.progress.EmptyProgressIndicator
//import com.intellij.openapi.project.Project
//import com.intellij.openapi.util.Disposer
//import com.intellij.platform.ide.progress.withBackgroundProgress
//import com.intellij.platform.util.coroutines.childScope
//import com.intellij.platform.util.progress.indeterminateStep
//import com.intellij.platform.util.progress.reportSequentialProgress
//import git4idea.remote.hosting.GitRemoteBranchesUtil
//import git4idea.remote.hosting.infoStateIn
//import git4idea.repo.GitRepository
//import kotlinx.coroutines.CoroutineName
//import kotlinx.coroutines.CoroutineScope
//import kotlinx.coroutines.Dispatchers
//import kotlinx.coroutines.ExperimentalCoroutinesApi
//import kotlinx.coroutines.channels.awaitClose
//import kotlinx.coroutines.flow.*
//import kotlinx.coroutines.future.asDeferred
//import kotlinx.coroutines.future.await
//import com.linqingying.gitcode.api.GitCodeServerPath
//import com.linqingying.gitcode.api.data.pullrequest.GCPullRequest
//import com.linqingying.gitcode.i18n.GitCodeBundle
//import com.linqingying.gitcode.pullrequest.comment.GCMarkdownToHtmlConverter
//import com.linqingying.gitcode.pullrequest.comment.GCSuggestedChange
//import com.linqingying.gitcode.pullrequest.comment.GCSuggestedChangeApplier
//import com.linqingying.gitcode.pullrequest.data.GCPRDataContext
//import com.linqingying.gitcode.pullrequest.data.provider.GCPRDataProvider
//import com.linqingying.gitcode.pullrequest.data.provider.GCPRDetailsDataProvider
//import com.linqingying.gitcode.pullrequest.data.provider.createThreadsRequestsFlow
//import com.linqingying.gitcode.pullrequest.ui.details.model.GCPRBranchesViewModel.Companion.getHeadRemoteDescriptor
//
//private val LOG = logger<GCPRReviewCommentBodyViewModel>()
//
//class GCPRReviewCommentBodyViewModel internal constructor(
//  parentCs: CoroutineScope,
//  val project: Project,
//  dataContext: GCPRDataContext,
//  dataProvider: GCPRDataProvider,
//  private val threadId: String,
//  private val commentId: String
//) {
//  private val cs = parentCs.childScope(CoroutineName("GC PR comment suggestion VM"))
//  private val detailsData = dataProvider.detailsData
//  private val reviewData = dataProvider.reviewData
//
//  private val taskLauncher = SingleCoroutineLauncher(cs)
//
//  val htmlImageLoader: AsyncHtmlImageLoader = dataContext.htmlImageLoader
//  private val server: GitCodeServerPath = dataContext.repositoryDataService.repositoryMapping.repository.serverPath
//  private val repository: GitRepository = dataContext.repositoryDataService.remoteCoordinates.repository
//
//  private val threadData = MutableStateFlow<ThreadData?>(null)
//  private val canResolvedThread = MutableStateFlow(false)
//  val body: StateFlow<String>
//
//  init {
//    body = MutableStateFlow("")
//    reviewData.createThreadsRequestsFlow().computationState().mapNotNull { it.getOrNull() }.onEach { threads ->
//      val thread = threads.find { it.id == threadId }
//      threadData.value = thread?.let {
//        val lineCount = (if (it.line != null && it.startLine != null) {
//          it.line - it.startLine
//        }
//        else if (it.originalLine != null && it.originalStartLine != null) {
//          it.originalLine - it.originalStartLine
//        }
//        else {
//          1
//        }).coerceAtLeast(1)
//        val originalEndLineIndex = it.originalLine?.dec()
//        val originalStartLineIndex = it.originalStartLine?.dec() ?: originalEndLineIndex
//        val endLineIndex = it.line?.dec()
//        val startLineIndex = it.startLine?.dec() ?: endLineIndex
//        ThreadData(it.isResolved, it.path, it.diffHunk,
//                   lineCount,
//                   originalStartLineIndex,
//                   originalEndLineIndex,
//                   startLineIndex,
//                   endLineIndex)
//      }
//      canResolvedThread.value = thread?.viewerCanResolve ?: false
//
//      body.value = thread?.comments?.find { it.id == commentId }?.body.orEmpty()
//    }.launchNowIn(cs)
//  }
//
//  val blocks: StateFlow<List<GCPRCommentBodyBlock>> = threadData.combineState(body) { thread, body ->
//    if (thread == null) return@combineState emptyList()
//    val markdownConverter = GCMarkdownToHtmlConverter(project)
//    val suggestions = body.getSuggestions()
//    if (suggestions.isEmpty()) {
//      val html = markdownConverter.convertMarkdown(body)
//      return@combineState listOf(GCPRCommentBodyBlock.HTML(html))
//    }
//    else {
//      val patchReader = PatchReader(PatchHunkUtil.createPatchFromHunk("_", thread.diffHunk))
//      val hunk = patchReader.readTextPatches().firstOrNull()?.hunks?.firstOrNull() ?: run {
//        LOG.warn("Empty diff hunk for thread $thread")
//        val html = markdownConverter.convertMarkdown(body)
//        return@combineState listOf(GCPRCommentBodyBlock.HTML(html))
//      }
//      val code = hunk.lines
//        .filter { it.type != PatchLine.Type.REMOVE }
//        .takeLast(thread.codeLinesCount)
//        .joinToString(separator = "\n") { it.text }
//
//      val htmlBody = markdownConverter.convertMarkdownWithSuggestedChange(body, thread.filePath, code)
//      val content = htmlBody.removePrefix("<body>").removeSuffix("</body>")
//      val blocks = GCPRReviewCommentBodyComponentFactory.collectCommentBlocks(content)
//      var suggestionIdx = 0
//      blocks.map {
//        when (it.commentType) {
//          GCPRReviewCommentBodyComponentFactory.CommentType.COMMENT -> {
//            GCPRCommentBodyBlock.HTML(it.content)
//          }
//          GCPRReviewCommentBodyComponentFactory.CommentType.SUGGESTED_CHANGE -> {
//            val suggestion = suggestions.getOrNull(suggestionIdx)
//            if (suggestion == null) {
//              LOG.warn("Missing suggestion by index $suggestionIdx\nBody:\n$body\n\nContent:\n${it.content}")
//            }
//            try {
//              createSuggestionBlock(it.content, thread, suggestion, hunk)
//            }
//            finally {
//              suggestionIdx++
//            }
//          }
//        }
//      }
//    }
//  }
//
//  private val loadedDetailsState = detailsData.createLoadedDetailsStateIn(cs)
//  val isOnReviewBranch: StateFlow<Boolean> = repository.infoStateIn(cs)
//    .combineState(loadedDetailsState) { _, details ->
//      val remote = details?.getHeadRemoteDescriptor(server) ?: return@combineState false
//      GitRemoteBranchesUtil.isRemoteBranchCheckedOut(repository, remote, details.headRefName)
//    }
//
//  fun applySuggestionLocally(patch: TextFilePatch) {
//    taskLauncher.launch(Dispatchers.Default) {
//      try {
//        withBackgroundProgress(project, GitCodeBundle.message("pull.request.timeline.comment.suggested.changes.progress.bar.apply")) {
//          val applyStatus = indeterminateStep(GitCodeBundle.message("pull.request.comment.suggested.changes.applying")) {
//            GCSuggestedChangeApplier.applySuggestedChange(project, repository, patch)
//          }
//          if (applyStatus == ApplyPatchStatus.SUCCESS && canResolvedThread.value) {
//            indeterminateStep(GitCodeBundle.message("pull.request.comment.suggested.changes.resolving")) {
//              reviewData.resolveThread(EmptyProgressIndicator(), threadId).asDeferred().await()
//            }
//          }
//        }
//      }
//      catch (e: Exception) {
//        LOG.warn("Failed to apply suggested change\n${patch.hunks.joinToString("\n\n") { it.text }}", e)
//      }
//    }
//  }
//
//  fun commitSuggestion(patch: TextFilePatch, commitMessage: String) {
//    taskLauncher.launch(Dispatchers.Default) {
//      try {
//        withBackgroundProgress(project, GitCodeBundle.message("pull.request.timeline.comment.suggested.changes.progress.bar.commit")) {
//          reportSequentialProgress { reporter ->
//            reporter.indeterminateStep(GitCodeBundle.message("pull.request.comment.suggested.changes.committing"))
//            val applyStatus = GCSuggestedChangeApplier.commitSuggestedChanges(project, repository, patch, commitMessage)
//            if (applyStatus == ApplyPatchStatus.SUCCESS && canResolvedThread.value) {
//              reporter.indeterminateStep(GitCodeBundle.message("pull.request.comment.suggested.changes.resolving"))
//              reviewData.resolveThread(EmptyProgressIndicator(), threadId).asDeferred().await()
//            }
//          }
//        }
//      }
//      catch (e: Exception) {
//        LOG.warn("Failed to apply suggested change\n${patch.hunks.joinToString("\n\n") { it.text }}", e)
//      }
//    }
//  }
//
//  private fun createSuggestionBlock(htmlContent: String,
//                                    thread: ThreadData,
//                                    suggestion: List<String>?,
//                                    hunk: PatchHunk): GCPRCommentBodyBlock.SuggestedChange {
//    val applicability: GCPRCommentBodyBlock.SuggestionsApplicability
//    val patch: TextFilePatch?
//    if (thread.resolved) {
//      applicability = GCPRCommentBodyBlock.SuggestionsApplicability.RESOLVED
//      patch = null
//    }
//    else if (thread.originalStartLineIndex == null || thread.originalEndLineIndex == null ||
//             thread.startLineIndex == null || thread.endLineIndex == null) {
//      applicability = GCPRCommentBodyBlock.SuggestionsApplicability.OUTDATED
//      patch = null
//    }
//    else {
//      applicability = GCPRCommentBodyBlock.SuggestionsApplicability.APPLICABLE
//      if (suggestion == null) {
//        patch = null
//      }
//      else {
//        val suggestionIndexShift = (suggestion.size - 1).coerceAtLeast(0)
//        val suggestedChangePatchHunk = PatchHunk(thread.startLineIndex, thread.endLineIndex,
//                                                 thread.startLineIndex, thread.startLineIndex + suggestionIndexShift)
//        getLinesInRange(hunk, Side.RIGHT, LineRange(thread.originalStartLineIndex, thread.originalEndLineIndex + 1)).forEach { line ->
//          suggestedChangePatchHunk.addLine(PatchLine(PatchLine.Type.REMOVE, line.text))
//        }
//        suggestion.forEach {
//          suggestedChangePatchHunk.addLine(PatchLine(PatchLine.Type.ADD, it))
//        }
//        patch = TextFilePatch(Charsets.UTF_8).apply {
//          beforeName = thread.filePath
//          afterName = thread.filePath
//          addHunk(suggestedChangePatchHunk)
//        }
//      }
//    }
//    return GCPRCommentBodyBlock.SuggestedChange(htmlContent, patch, applicability)
//  }
//
//  private data class ThreadData(
//    val resolved: Boolean,
//    val filePath: String,
//    val diffHunk: String,
//    val codeLinesCount: Int,
//    val originalStartLineIndex: Int?,
//    val originalEndLineIndex: Int?,
//    val startLineIndex: Int?,
//    val endLineIndex: Int?
//  )
//}
//
//@OptIn(ExperimentalCoroutinesApi::class)
//private fun GCPRDetailsDataProvider.createLoadedDetailsStateIn(cs: CoroutineScope): StateFlow<GCPullRequest?> =
//  callbackFlow {
//    val disposable = Disposer.newDisposable()
//    addDetailsLoadedListener(disposable) {
//      trySend(loadDetails())
//    }
//    send(loadDetails())
//    awaitClose { Disposer.dispose(disposable) }
//  }.mapLatest {
//    runCatching {
//      it.await()
//    }.getOrNull()
//  }.stateIn(cs, SharingStarted.Eagerly, loadedDetails)
//
//private fun String.getSuggestions(): List<List<String>> {
//  val result = mutableListOf<List<String>>()
//  var intermediateResult: MutableList<String>? = null
//  for (line in lines()) {
//    if (intermediateResult == null && line.startsWith(GCSuggestedChange.SUGGESTION_BLOCK_START)) {
//      intermediateResult = mutableListOf()
//      continue
//    }
//    else if (intermediateResult != null && line.endsWith(GCSuggestedChange.SUGGESTION_BLOCK_END)) {
//      result.add(intermediateResult)
//      intermediateResult = null
//      continue
//    }
//
//    intermediateResult?.add(line)
//  }
//  return result
//}
//
//sealed interface GCPRCommentBodyBlock {
//  data class HTML(val body: String) : GCPRCommentBodyBlock
//  data class SuggestedChange(
//    val bodyHtml: String,
//    val patch: TextFilePatch?,
//    val applicability: SuggestionsApplicability
//  ) : GCPRCommentBodyBlock
//
//  enum class SuggestionsApplicability {
//    APPLICABLE, RESOLVED, OUTDATED
//  }
//}
